package org.bjf.utils;

import com.google.common.base.Preconditions;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.bjf.config.ScriptConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.stereotype.Component;

/**
 * redis 工具类 理论上不允许直接使用RedisTemplate 要求所有暴露方法都要有超时时间，避免不设置超时redis库爆掉
 */
@Component
@Slf4j
public class RedisUtil {

  @Autowired
  protected RedisTemplate redisTemplate;


  /**
   * 分布式锁
   */
  public boolean getLock(String key, long timeout) {
    return redisTemplate.opsForValue().setIfAbsent(key, "lock", timeout, TimeUnit.SECONDS);
  }
  /**
   * 基于redis lua脚本的分布式锁
   */
  public boolean getLockLua(String key, long timeout) {
    List<String> keys = Arrays.asList(key);
    try {
      Long lockVal = (Long) redisTemplate.execute(ScriptConfig.LOCK_SCRIPT, keys, timeout);
      if (lockVal > 0) {
        return Boolean.TRUE;
      }
    } catch (Exception e) {
      log.error("获取锁异常", e);
    }
    return Boolean.FALSE;
  }

  /**
   * 根据给定的布隆过滤器添加值
   */
  public <T> void putBloomFilter(BloomFilterHelper<T> bloomFilterHelper, String key, T value) {
    Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能为空");
    int[] offset = bloomFilterHelper.getHashOffset(value);
    for (int i : offset) {
      redisTemplate.opsForValue().setBit(key, i, Boolean.TRUE);
    }
  }

  /**
   * 根据给定的布隆过滤器判断值是否存在
   */
  public <T> boolean mightContain(BloomFilterHelper<T> bloomFilterHelper, String key, T value) {
    Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能为空");
    int[] offset = bloomFilterHelper.getHashOffset(value);
    for (int i : offset) {
      if (!redisTemplate.opsForValue().getBit(key, i)) {
        return Boolean.FALSE;
      }
    }
    return Boolean.TRUE;
  }

  public void setex(String key, Object value, long timeout) {
    redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
  }

  public <T> T get(String key, Class<T> clz) {
    Object value = redisTemplate.opsForValue().get(key);
    if (value == null) {
      return null;
    }
    if (Long.class.isAssignableFrom(clz)) {
      return (T) Long.valueOf(value.toString());
    }
    return clz.cast(value);
  }

  public Set<String> keys(String pattern) {
    return redisTemplate.keys(pattern);
  }

  public void delete(String key) {
    redisTemplate.delete(key);
  }

  public boolean exists(final String key) {
    return redisTemplate.hasKey(key);
  }

  public Long incr(String key, long num) {
    return redisTemplate.opsForValue().increment(key, num);
  }

  public Double incr(String key, double num) {
    return redisTemplate.opsForValue().increment(key, num);
  }

  /**
   * ===========================redis zet 排行榜===========================
   */
  public void zAdd(String key, Object name, double score) {
    redisTemplate.opsForZSet().add(key, name, score);
  }

  /**
   * 查询排名
   */
  public Set zReverseRange(String key, long start, long end) {
    return redisTemplate.opsForZSet().reverseRange(key, start, end);
  }
  /**
   * 查询排名列表
   */
  public Set<TypedTuple> zReverseRangeWithScores(String key, long start, long end) {
    return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
  }

  /**
   * 查询分数
   */
  public Double zScore(String key, Object name) {
    return redisTemplate.opsForZSet().score(key, name);
  }

  /**
   * 查询个人排名
   */
  public Long zReverseRank(String key, Object name) {
    return redisTemplate.opsForZSet().reverseRank(key, name);
  }
  /**
   * ===========================原生接口 exec开头===========================
   */
  public void execSetex(final String key, final String value, final long timeout) {

    redisTemplate.execute(new RedisCallback() {
      @Override
      public Object doInRedis(RedisConnection conn) throws DataAccessException {
        conn.setEx(key.getBytes(), timeout, value.getBytes());
        return 1L;
      }
    });
  }

}
